// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.api.ads.dfa.matchtables;

import au.com.bytecode.opencsv.CSVWriter;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * Handles match table writing and exposes enumeration of all match table file types and fields.
 *
 * @author Moshe Kahan
 */
class MatchTable {

  private static final String[] ACTIVITY_CAT_FIELDS = {
    "Spot-ID",
    "Activity-Type-ID",
    "Activity-Type",
    "Activity-ID",
    "Activity-Sub-Type",
    "Report-Name",
    "Tag-Method-ID"
  };

  private static final String[] ACTIVITY_TYPE_FIELDS = {
    "Spot-ID",
    "Activity-Type-ID",
    "Activity-Type",
    "Report-Name"
  };

  private static final String[] AD_FIELDS =  {
    "Advertiser-ID",
    "Buy-ID",
    "Ad-ID",
    "Ad-Description",
    "Click-URL",
    "Ad-Type",
    "Creative-Size-ID",
    "Ad-Comments"
  };

  private static final String[] AD_PAGE_ASSIGNMENTS_FIELDS = {
    "Ad-ID",
    "Page-ID"
  };

  private static final String[] ADVERTISER_FIELDS = {
    "Spot-ID",
    "Advertiser-ID",
    "Advertiser",
    "Advertiser-Group-ID",
    "Advertiser-Group"
  };

  private static final String[] BROWSERS_FIELDS = {
    "Browser-ID",
    "Browser"
  };

  private static final String[] CAMPAIGN_FIELDS = {
    "Advertiser-ID",
    "Buy-ID",
    "Buy",
    "Start-Date",
    "End-Date",
    "Creative-Library-Enabled",
    "Billing-Invoice-Code"
  };

  private static final String[] CITIES_FIELDS = {
    "City-ID",
    "City"
  };

  private static final String[] COUNTRIES_FIELDS = {
    "Country-ID",
    "Country"
  };

  private static final String[] CREATIVE_FIELDS = {
    "Advertiser-ID",
    "Creative-ID",
    "UI-Creative-ID",
    "Creative",
    "Last-Modified-Date",
    "Creative-Type",
    "Creative-Sub-Type",
    "Creative-Size-ID",
    "Image-URL",
    "Creative-Version"
  };

  private static final String[] CREATIVE_AD_ASSIGNMENTS_FIELDS = {
    "Ad-ID",
    "Creative-ID",
    "Start-Date",
    "End-Date",
    "Rotation-Type",
    "Creative-Group-1",
    "Creative-Group-2",
    "Click-URL"
  };

  private static final String[] CUSTOM_CREATIVE_FIELDS_FIELDS = {
    "Advertiser-ID",
    "Creative-ID",
    "Custom-Field-Number",
    "Custom-Field-Name",
    "Custom-Field-Value"
  };

  private static final String[] DARTSEARCH_FIELDS = {
    "Ad-ID",
    "Advertiser-ID",
    "Buy-ID",
    "Keyword-ID",
    "Extended-Keyword-ID",
    "DS-Campaign-Name",
    "KW-Ad-Group-Name",
    "Bid-Strategy-Name",
    "Landing-Page-URL",
    "Keyword-Name",
    "Match-Type"
  };

  private static final String[] DESIGNATED_MARKET_AREA_FIELDS = {
    "DMA-ID",
    "DMA"
  };

  private static final String[] KEYVAL_FIELDS = {
    "Ad-ID",
    "Keyword-Expression"
  };

  private static final String[] MET_EVENT_FIELDS = {
    "Advertiser-ID",
    "Met-Event-ID",
    "Met-Event-Name",
    "Met-Event-Type-ID",
    "Met-Event-Type-Name"
  };

  private static final String[] OPERATING_SYSTEMS_FIELDS = {
    "OS-ID",
    "OS"
  };

  private static final String[] PAGE_FIELDS = {
    "Buy-ID",
    "Site-ID",
    "Page-ID",
    "Page",
    "Site-Placement",
    "Content-Category",
    "Strategy",
    "Start-Date",
    "End-Date",
    "Group-Type",
    "Group-Parent-ID",
    "Pricing-Type",
    "Cap-Cost",
    "Purchase-Quantity",
    "Purchase-Cost",
    "Flighting-Activated",
    "CPA-Activity-ID"
  };

  private static final String[] PAGE_COST_FIELDS = {
    "Page-ID",
    "Effective-Date",
    "End-Date",
    "Units",
    "Rate",
    "Comments"
  };

  private static final String[] PAGE_FLIGHT_COST_FIELDS = {
    "Page-ID",
    "Effective-Date",
    "End-Date",
    "Units",
    "Rate",
    "Comments"
  };

  private static final String[] SITE_FIELDS = {
    "Site-ID",
    "Site",
    "Directory-Site-ID",
    "Directory-Site"
  };

  private static final String[] STATES_FIELDS = {
    "State/Province",
    "State/Province Name"
  };

  private static final String[] USER_DEFINED_SPOT_VARS_FIELDS = { 
    "Spot-ID",
    "Variable-ID",
    "Variable-Name"
  };

  private Type type;
  private Integer[] fieldSubsetArray;
  private CSVWriter writer;
  private char outputDelimiter;

  enum Type {
    ACTIVITY_CAT("activity_cat", ACTIVITY_CAT_FIELDS),
    ACTIVITY_TYPE("activity_type", ACTIVITY_TYPE_FIELDS),
    AD("ad", AD_FIELDS),
    AD_PAGE_ASSIGNMENTS("ad_page_assignments", AD_PAGE_ASSIGNMENTS_FIELDS),
    ADVERTISER("advertiser", ADVERTISER_FIELDS),
    BROWSERS("browsers", BROWSERS_FIELDS),
    CAMPAIGN("campaign", CAMPAIGN_FIELDS),
    CITIES("cities", CITIES_FIELDS),
    COUNTRIES("countries", COUNTRIES_FIELDS),
    CREATIVE("creative", CREATIVE_FIELDS),
    CREATIVE_AD_ASSIGNMENTS("creative_ad_assignments", CREATIVE_AD_ASSIGNMENTS_FIELDS),
    CUSTOM_CREATIVE_FIELDS("custom_creative_fields", CUSTOM_CREATIVE_FIELDS_FIELDS),
    DARTSEARCH("dartsearch", DARTSEARCH_FIELDS),
    DESIGNATED_MARKET_AREA("designated_market_area", DESIGNATED_MARKET_AREA_FIELDS),
    KEYVAL("keyval", KEYVAL_FIELDS),
    MET_EVENT("met_event", MET_EVENT_FIELDS),
    OPERATING_SYSTEMS("operating_systems", OPERATING_SYSTEMS_FIELDS),
    PAGE("page", PAGE_FIELDS),
    PAGE_COST("page_cost", PAGE_COST_FIELDS),
    PAGE_FLIGHT_COST("page_flight_cost", PAGE_FLIGHT_COST_FIELDS),
    SITE("site", SITE_FIELDS),
    STATES("states", STATES_FIELDS),
    USER_DEFINED_SPOT_VARS("user_defined_spot_vars", USER_DEFINED_SPOT_VARS_FIELDS);

    Type(String tableName, String[] fields) {
      this.tableName = tableName;
      this.fields = fields;
    }

    String getTableName() {
      return tableName;
    }

    String[] getFields() {
      return fields;
    }

    private String tableName;
    private String[] fields;
  }

  /**
   * Constructs a MatchTable to a specified type and field subset.
   * @param type will map to match table file name and possible fields names
   * @param fieldsToGenerate subset of possible match table fields to generate
   * @param outputDelimiter character used to delimit the match table fields
   */
  MatchTable(Type type, Set<String> fieldsToGenerate, char outputDelimiter) {
    this.type = type;
    List<Integer> fieldList = new ArrayList<Integer>();
    for (int index = 0; index < type.getFields().length; ++index) {
      String field = type.getFields()[index];
      if (fieldsToGenerate.contains(field)) {
        fieldList.add(index);
      }
    }
    fieldSubsetArray = fieldList.toArray(new Integer[0]);
    this.outputDelimiter = outputDelimiter;
  }

  /**
   * Open a new match table in the path specified. Writes header row.
   * @param outputPath location of match table file
   * @throws MatchTableFileException if error encountered opening MT file
   */
  void open(String outputPath) throws MatchTableFileException {
    File path = new File(outputPath);
    File file = new File(path, type.getTableName() + ".log");
    try {
      open(new FileWriter(file));
    } catch (IOException e) {
      throw new MatchTableFileException("Error attempting to open match table file", e);
    }
  }

  // For testing purposes.
  void open(OutputStream outputStream) {
    open(new OutputStreamWriter(outputStream));
  }

  // For testing purposes.
  void open(Writer ioWriter) {
    writer = new CSVWriter(ioWriter, outputDelimiter, CSVWriter.NO_QUOTE_CHARACTER,
        CSVWriter.NO_ESCAPE_CHARACTER);
    write(type.getFields());
  }

  /**
   * Write match table row. String array needs to be match exactly field name array associated with
   * this match table's type. Will then remove values that are not in field subset array for this
   * particular match table object. Strings that are null will result in an empty value for that
   * field.
   * @param values array of field values for this match table row. Should match order of field name
   *    array of this MatchTable's type.
   */
  void write(String ... values) {
    if (values == null || values.length != type.getFields().length) {
      throw new IllegalArgumentException("Internal Error!"
          + " Array of values sent to match table does not correspont to type: "
          + type.getTableName());
    }
    String[] fields = new String[fieldSubsetArray.length];
    for (int i = 0; i < fieldSubsetArray.length; ++i) {
      String value = values[fieldSubsetArray[i]];
      if (value == null) {
        value = "";
      }
      fields[i] = value;
    }
    writer.writeNext(fields);
  }

  /**
   * Close the match table file.
   * @throws MatchTableFileException if error encountered closing MT file
   */
  void close() throws MatchTableFileException {
    try {
      writer.close();
    } catch (IOException e) {
      throw new MatchTableFileException("Error attempting to close match table file", e);
    }
  }

  /**
   * Flush the match table file.
   * @throws MatchTableFileException if error encountered flushing MT file
   */
  void flush() throws MatchTableFileException {
    try {
      writer.flush();
    } catch (IOException e) {
      throw new MatchTableFileException("Error attempting to flush match table file", e);
    }
  }

  String getName() {
    return type.getTableName();
  }

  boolean hasField(String field) {
    for (int index : fieldSubsetArray) {
      if (type.getFields()[index].equals(field)) {
        return true;
      }
    }
    return false;
  }
}
